/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.ignition.transport;

import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.axiom.sso.Session;
import com.vii.brillien.kernel.axiom.transport.Communication;
import com.vii.brillien.kernel.axiom.transport.CommunicationListener;
import com.vii.brillien.kernel.axiom.transport.Mediator;
import com.vii.brillien.kernel.axiom.transport.MessageProcessor;
import com.vii.streamline.services.ThreadServices;
import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.services.json.JsonServices;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;

/**
 * Abstract implementation of Mediator defined by the module clarity.
 * It declares the commons functionality for all concrete implementation.
 */
public abstract class BrillienMediator<T> implements Mediator<BrillienCommunication, T> {

    /**
     * Queue of registered messageprocessors
     */
    protected BlockingQueue<ProcessorReference>         messageProcessorTypes;

    /**
     * Queue of registered communicationlisteners
     */
    protected BlockingQueue<CommunicationListener<T>>   communicationListeners;


    /**
     * LoaderComplyer instance to able to access local classes of components
     */
    protected LoaderComplyer                            loaderComplyer;

    {
        messageProcessorTypes   = new LinkedBlockingQueue<ProcessorReference>();
        communicationListeners  = new LinkedBlockingQueue<CommunicationListener<T>>();

        loaderComplyer = new LoaderComplyer(){
            @Override
            public ClassLoader getLoader(String reference) {
                return this.getClass().getClassLoader();
            }
        };
    }

    public void setLoaderComplyer(LoaderComplyer loaderComplyer) {
        this.loaderComplyer = loaderComplyer;
    }

    /**
     * Creates a new instance of a specific Communication implementation
     */
    public abstract BrillienCommunication newCommunication() throws BrillienException;

    public BrillienCommunication newErrorCommunication(int code, String message, Object value) throws BrillienException{
        BrillienCommunication comm = newCommunication();
        comm.setMessageType( Communication.TYPE_ERROR );
        comm.setErrorCode( code );
        comm.setResponse( message );
        if( value != null )
            try {
                comm.setErrorValue( JsonServices.toJSON( value ) );
            } catch (StreamLineException e) {
                throw new BrillienException( e.getErrorCode(), e.getMessage(), e.getPayLoad() );
            }
        return comm;
    }

    protected BrillienCommunication getCachedResponse(String to, String message, Map<String, Object> parameters){
        if( BrillienContext.API_CACHING &&
                (
                    message.equals("hasMessageProcessorReturnValue") ||
                    message.equals("getMessageProcessorParameterNames") ||
                    message.equals("getMessageProcessorParameterTypes")
                )
        ){
            String serviceName = (parameters != null && parameters.containsKey("processorServiceName")) ? parameters.get("processorServiceName").toString() : null;
            if( serviceName != null )
                return BrillienCache.<BrillienCommunication>getCachedResponse(to, message, serviceName);
        }
        return null;
    }

    protected BrillienCommunication cacheResponse(String to, String message, Map<String, Object> parameters, BrillienCommunication response){
        if( BrillienContext.API_CACHING &&
                (
                    message.equals("hasMessageProcessorReturnValue") ||
                    message.equals("getMessageProcessorParameterNames") ||
                    message.equals("getMessageProcessorParameterTypes")
                )
        ){
            String serviceName = (parameters != null && parameters.containsKey("processorServiceName")) ? parameters.get("processorServiceName").toString() : null;
            if( serviceName != null && response != null )
                BrillienCache.<BrillienCommunication>cacheResponse(to, message, serviceName, response);
        }
        return response;
    }


    /**
     * Retrieves the String representing the Message to be sent
     */
    protected String getMessageToSend(BrillienCommunication comm) throws BrillienException {
        try {
            return JsonServices.toJSON( comm );
        } catch (Exception e) {
            throw new BrillienException(e);
        }
    }

    @Override
    public <M extends MessageProcessor> void addMessageProcessorType(Class<M> messageProcessorType) {
        messageProcessorTypes.offer( new ProcessorReference<M>( messageProcessorType ) );
    }

    @Override
    public <M extends MessageProcessor> void addMessageProcessorType(Class<M> messageProcessorType, Object outerInstance) {
        messageProcessorTypes.offer( new ProcessorReference<M>( messageProcessorType, outerInstance) );
    }

    @Override
    public <M extends MessageProcessor> Iterable<M> getNewMessageProcessorInstances(BrillienCommunication comm) throws BrillienException {
        try{
            LinkedList<M> list = new LinkedList<M>();
            for( ProcessorReference<M> reference : messageProcessorTypes ){
                M processor = reference.newInstance();
                processor.init( comm );
                list.add( processor );
            }
            return list;
        } catch(Exception e){
            throw new BrillienException( e );
        }
    }

    @Override
    public <M extends CommunicationListener<T>> void addCommunicationListener(M messageProcessor) {
        communicationListeners.offer(messageProcessor);
    }

    @Override
    public <M extends CommunicationListener<T>> void removeCommunicationListener(M messageProcessor) {
        communicationListeners.remove(messageProcessor);
    }

    @Override
    public Iterable<CommunicationListener<T>> getCommunicationListeners() {
        return communicationListeners;
    }

    @Override
    public BrillienCommunication prepareNewCommunication( ) throws BrillienException {
        BrillienCommunication com = newCommunication();

        return com;
    }

    @Override
    public BrillienCommunication prepareNewCommunication( String sender, String flowID, String recipient, String subject, int type ) throws BrillienException{
        return prepareNewCommunication((Session) null, sender, flowID, recipient, subject, type);
    }


    @Override
    public BrillienCommunication prepareNewCommunication( BrillienCommunication comm, String sender, String flowID, String recipient, String subject, int type ) throws BrillienException{
        return prepareNewCommunication((Session) null, comm, sender, flowID, recipient, subject, type);
    }

    @Override
    public BrillienCommunication prepareNewCommunication( Session session, String sender, String flowID, String recipient, String subject, int type ) throws BrillienException{
        return prepareNewCommunication(session, null, sender, flowID, recipient, subject, type);
    }

    @Override
    public BrillienCommunication prepareNewCommunication( Session session, BrillienCommunication comm, String sender, String flowID, String recipient, String subject, int type ) throws BrillienException{
        return prepareNewCommunication(session, comm, sender, flowID, recipient, subject, type, null);
    }

    @Override
    public BrillienCommunication prepareNewCommunication( String sender, String flowID, String recipient, String subject, int type, Map<String, Object> parameters ) throws BrillienException{
        return prepareNewCommunication((Session) null, sender, flowID, recipient, subject, type, parameters);
    }

    @Override
    public BrillienCommunication prepareNewCommunication( BrillienCommunication comm, String sender, String flowID, String recipient, String subject, int type, Map<String, Object> parameters ) throws BrillienException{
        return prepareNewCommunication((Session) null, comm, sender, flowID, recipient, subject, type, parameters);
    }

    @Override
    public BrillienCommunication prepareNewCommunication( Session session, String sender, String flowID, String recipient, String subject, int type, Map<String, Object> parameters ) throws BrillienException{
        return prepareNewCommunication(session, null, sender, flowID, recipient, subject, type, parameters);
    }

    @Override
    public BrillienCommunication prepareNewCommunication( Session session, BrillienCommunication comm, String sender, String flowID, String recipient, String subject, int type, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication newComm = newCommunication();

        newComm.setSession(session);
        newComm.setSender(sender);
        newComm.setFlowID(flowID);
        newComm.setRecipient(recipient);
        newComm.setSubject(subject);
        newComm.setMessageType(type);
        
        if (parameters != null )
            newComm.setParameters( parameters );

        return newComm;
    }

    @Override
    public void receiveCommunication(BrillienCommunication comm) throws BrillienException {
        BrillienContext.basicLog( Level.FINE, mediatorEntity() + ":: " + "internal communication received " + comm );

        for( MessageProcessor processor : getNewMessageProcessorInstances( comm ) ){
            ThreadServices.getStpe().execute( processor );
        }
    }

    @Override
    public BrillienCommunication sendCommunication(BrillienCommunication comm) throws BrillienException {
        return sendCommunication(BrillienContext.COMMUNICATION_REPLY_TIMEOUT, null, comm);
    }

    @Override
    public BrillienCommunication sendCommunication(HashMap<String, Object> properties, BrillienCommunication comm) throws BrillienException {
        return sendCommunication(BrillienContext.COMMUNICATION_REPLY_TIMEOUT, properties, comm);
    }

    @Override
    public BrillienCommunication sendCommunication(long timeout, BrillienCommunication comm) throws BrillienException {
        return sendCommunication(timeout, null, comm);
    }

    protected boolean containsKey( Map<String, Object> parameters, String key ){
        for (String s : parameters.keySet()) {
            if( s.equalsIgnoreCase( key ) )
                return true;
        }
        return false;
    }

    protected Object get( Map<String, Object> parameters, String key ){
        for (String s : parameters.keySet()) {
            if( s.equalsIgnoreCase( key ) )
                return parameters.get( s );
        }
        return null;
    }

    /**
     * Retrieves parameters by mappings
     /
    protected Object[] getParameters( Session session, String to, String message, Map<String, Object> parameters ) throws BrillienException {
        BrillienContext.systemLog(Level.FINE, "Converting map to parameter list to" + to + " about " + message, parameters.toString() );

        C c = sendGet( session, to, "getMessageProcessorParameterNames", new InnerMap<String, Object>("processorServiceName", message) );
        List<String> formalParameterNames = c.acquireResponse( List.class );

        BrillienContext.systemLog(Level.FINE, "Retrieved formalParameterNames", formalParameterNames.toString());

        c = sendGet( session, to, "getMessageProcessorParameterTypes", new InnerMap<String, Object>("processorServiceName", message) );
        List<String> formalParameterTypes = c.acquireResponse( List.class );

        BrillienContext.systemLog(Level.FINE, "Retrieved formalParameterTypes", formalParameterTypes.toString() );

        Object[] os = new Object[ formalParameterNames.size() ];
        for( int i=0; i<os.length; ++i ){
            if( !containsKey( parameters, formalParameterNames.get(i) ) )
                throw new BrillienException( "No parameter found to this formal parameter:" + formalParameterNames.get(i) );

            try{
                Object o = get( parameters, formalParameterNames.get(i) );

                BrillienContext.systemLog(Level.FINE,"Found parameter " + o + " for " + formalParameterNames.get(i));

                os[ i ] =
                        (
                            o instanceof HashMap &&
                            !formalParameterTypes.get(i).equals( HashMap.class.getName() )
                        )
                    ? TypeServices.fillObject( (HashMap) o, formalParameterTypes.get(i),
                                this.getClass().getClassLoader(), loaderComplyer.getLoader(to) )
                    : o;

                BrillienContext.systemLog(Level.FINE, "Added parameter " + os[ i ] );
            } catch( Exception e ){
                throw new BrillienException("Conversion cannot be made: " + formalParameterNames.get(i) + " " + formalParameterTypes.get(i), e );
            }
        }

        return os;
    }  */



    @Override
    public BrillienCommunication sendGet( Session session, String to, String message ) throws BrillienException{
        return sendGet(session, to, message, null);
    }

    @Override
    public BrillienCommunication sendGet( Session session, long timeout, String to, String message ) throws BrillienException{
        return sendGet(session, timeout, to, message, null);
    }

    @Override
    public void sendAGet( Session session, String to, String message, String redirectMessage ) throws BrillienException{
        sendAGet(session, to, message, redirectMessage, null);
    }

    @Override
    public void sendDelegatedGet( Session session, String to, String message, String redirectEntityName, String redirectMessage ) throws BrillienException{
        sendDelegatedGet(session, to, message, redirectEntityName, redirectMessage, null);
    }

    @Override
    public void sendSet( Session session, String to, String message ) throws BrillienException{
        sendSet(session, to, message, null);
    }

    @Override
    public BrillienCommunication sendGet( Session session, String to, String message, Map<String, Object> parameters ) throws BrillienException{
        return sendGet(session, BrillienContext.COMMUNICATION_REPLY_TIMEOUT, to, message, parameters);
    }

    @Override
    public BrillienCommunication sendGet( Session session, long timeout, String to, String message, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication c = this.getCachedResponse(to, message, parameters);
        if( c != null )
            return c;

        BrillienCommunication rpc = prepareNewCommunication(session,
                mediatorEntity(), null,
                to, message, Communication.TYPE_GET, parameters
        );

        return this.cacheResponse(to, message, parameters, sendCommunication(timeout, rpc));
    }

    @Override
    public void sendAGet( Session session, String to, String message, String redirectMessage, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication rpc = prepareNewCommunication(session,
                mediatorEntity(), null,
                to, message, Communication.TYPE_AGET, parameters
        );
        rpc.setRedirectMessage(redirectMessage);

        sendCommunication(rpc);
    }

    @Override
    public void sendDelegatedGet( Session session, String to, String message, String redirectEntityName, String redirectMessage, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication rpc = prepareNewCommunication(session,
                mediatorEntity(), null,
                to, message, Communication.TYPE_DELEGATED_SET, parameters
        );
        rpc.setRedirectEntityName( redirectEntityName );
        rpc.setRedirectMessage( redirectMessage );

        sendCommunication(rpc);
    }

    @Override
    public void sendSet( Session session, String to, String message, Map<String, Object> parameters ) throws BrillienException{
        BrillienCommunication rpc = prepareNewCommunication( session,
                mediatorEntity( ), null,
                to, message, Communication.TYPE_SET, parameters
        );

        sendCommunication(rpc);
    }

}
